package com.fa4j.common.web.config;

import com.fa4j.common.base.model.ModuleInfo;
import com.fa4j.common.base.util.ModuleUtil;
import com.fa4j.common.web.annotation.ApiMethod;
import com.fa4j.common.web.model.AppApi;
import com.fa4j.common.web.model.AppType;
import com.fa4j.common.web.model.CommonWebConst;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.configuration.SpringDocConfiguration;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

@Slf4j
@Configuration
@SecurityScheme(
        name = "Bearer Authentication",
        type = SecuritySchemeType.HTTP,
        scheme = "bearer"
)
@OpenAPIDefinition(
        info = @Info(title = "fa4j API")
)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(SpringDocConfiguration.class)
public class OpenApiConfiguration {

    @Bean
    public List<GroupedOpenApi> groupedOpenApis(List<AppApi> apiInfos) {
        List<AppType> appTypes = apiInfos.stream()
                .flatMap(api -> Arrays.stream(api.getAppTypes())).distinct()
                .sorted(Comparator.comparing(AppType::getCode))
                .toList();
        List<GroupedOpenApi> list = new ArrayList<>();
        for (AppType type : appTypes) {
            list.add(GroupedOpenApi.builder()
                    .group(type.getName())
                    .addOpenApiMethodFilter(m -> {
                        ApiMethod apiMethod = m.getAnnotation(ApiMethod.class);
                        if (apiMethod != null) {
                            for (AppType appType : apiMethod.type()) {
                                if (appType == type) {
                                    return true;
                                }
                            }
                        }
                        return false;
                    })
                    .addOperationCustomizer((operation, handlerMethod) -> {

                        operation.addParametersItem(new Parameter()
                                .in("header")
                                .name(CommonWebConst.HEADER_APP_TYPE)
                                .description("应用类型")
                                .required(true)
                                .example(type.name())
                                .schema(new StringSchema()));

                        operation.addParametersItem(new Parameter()
                                .in("header")
                                .name(CommonWebConst.HEADER_APP_VER)
                                .description("应用版本")
                                .required(false)
                                .schema(new StringSchema()));

                        operation.addParametersItem(new Parameter()
                                .in("header")
                                .name(CommonWebConst.HEADER_APP_ENV)
                                .description("应用环境")
                                .required(false)
                                .schema(new StringSchema()));
                        ModuleInfo module = ModuleUtil.getModule(handlerMethod.getBeanType());
                        operation.setTags(List.of(module.getCode() + "-" + module.getDescription()));

                        ApiMethod apiMethod = handlerMethod.getMethodAnnotation(ApiMethod.class);
                        if (apiMethod != null && apiMethod.login() != ApiMethod.Switch.OFF) {
                            operation.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"));
                        }
                        return operation;
                    })
                    .build()
            );
        }
        return list;
    }
}
